/******************************************************************************/
package jamie.org.sil.www.usxUtils;

import java.io.*;
import java.util.TreeMap;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.*;

import org.w3c.dom.*;





/******************************************************************************/
/**
   Numeral processor factory.
   <p>

   This class is a factory which creates numeral processors &ndash;
   classes which take basic numeric values and convert them according
   to the number representation used in a particular vernacular language.
   <p>

   The class is implemented as a singleton.
   <p>


   <h3>Configuration information</h3>
   The class selects relevant information from an XML-based configuration
   file.
*/

public class NumeralProcessorFactory implements SpecialistConfigConsumer
{
  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                               Public                                   **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/

  /****************************************************************************/
  /**
     Singleton.

     @return Singleton instance.
  */

  public static NumeralProcessorFactory instance ()
  {
    if (null == m_Instance) m_Instance = new NumeralProcessorFactory();
    return m_Instance;
  }


  /****************************************************************************/
  /** Loads config data from the given file.
   *
   * @param fileName
   * @param type Type of config information available from this file.
   * @throws ConverterException
   * @throws IOException
   * @throws javax.xml.parsers.ParserConfigurationException
   * @throws org.xml.sax.SAXException
   */

  @Override public void load (String fileName, String type) throws ConverterException,
                                                                   IOException,
                                                                   javax.xml.parsers.ParserConfigurationException,
                                                                   org.xml.sax.SAXException
  {
    /******************************************************************/
    Document document = null;
    TreeMap<Integer, String> glyphs = new TreeMap<Integer, String>();
    NumeralProcessor res = null;
    String systemBase = "";
    String systemName = Config.instance().getNumberSystem();
    String textDir = "";



    /******************************************************************/
    try
    {
      System.setProperty("javax.xml.parsers.DocumentBuilderFactory",
                         "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");

      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      factory.setNamespaceAware(true);
      //factory.setXIncludeAware(true);

      DocumentBuilder builder = factory.newDocumentBuilder();
      document = builder.parse(new File(fileName));
      document.getDocumentElement().normalize();
    }
    catch (Exception e)
    {
      throw new ConverterException(ConverterException.Action.AbandonRunWhenever, ConverterException.Log.Error, "Couldn't read numeral information: " + e.getMessage());
    }



    /******************************************************************/
    try
    {
      XPathExpression expr = m_Xpath.compile("//system[@name=\"" + systemName +"\"]/textDir");
      NodeList nodeList = (NodeList) expr.evaluate(document, XPathConstants.NODESET);
      textDir = nodeList.item(0).getFirstChild().getNodeValue();

      expr = m_Xpath.compile("//system[@name=\"" + systemName +"\"]/systemBase");
      nodeList = (NodeList) expr.evaluate(document, XPathConstants.NODESET);
      systemBase = nodeList.item(0).getFirstChild().getNodeValue();

      expr = m_Xpath.compile("//system[@name=\"" + systemName +"\"]/digitGlyphSet/digit");
      nodeList = (NodeList) expr.evaluate(document, XPathConstants.NODESET);
      for (int i = 0; i < nodeList.getLength(); ++i)
        glyphs.put(Integer.parseInt(((Element) (nodeList.item(i))).getAttribute("number")),
                                    nodeList.item(i).getFirstChild().getNodeValue());
     }
    catch (Exception e)
    {
      throw new ConverterException(ConverterException.Action.AbandonRunWhenever, ConverterException.Log.Error, "Can't find full details for numbering scheme " + systemName);
    }



    /******************************************************************/
    String className = "";
    if (-1 != ".european.chinese.".indexOf("." + systemName.toLowerCase() + "."))
      className = "NumeralProcessor_" + systemName.substring(0, 1).toUpperCase() + systemName.substring(1).toLowerCase();
    else
      className = "NumeralProcessor_Positional_" + textDir.substring(0, 1).toUpperCase() + textDir.substring(1).toLowerCase();

    try
    {
      res =
        (NumeralProcessor)
          Class.forName("jamie.org.sil.www.usxUtils." + className).newInstance();
      res.initialise(glyphs, textDir, Integer.parseInt(systemBase));
    }
    catch (Exception e)
    {
      throw new ConverterException(ConverterException.Action.AbandonRunWhenever, ConverterException.Log.Error, "Don't have an implementation to handle numbering scheme " + systemName);
    }
  }





  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                               Private                                  **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/

  /**************************************************************************/
  /**
     Look up a given XPath in all the files until successful, and return
     the associated value.

     @param node Node from which to start search.

     @param path  XPath.

     @return Associated value.

     @throws ConverterException
  */

  private String getXpath (Node node, String path) throws ConverterException
  {
    XPathExpression expr = null;
    try { expr = m_Xpath.compile(path); } catch(Exception e) { throw new ConverterException(ConverterException.Action.AbandonRunNow, ConverterException.Log.Error, e.getMessage()); }

    try
    {
      NodeList nodeList = (NodeList) expr.evaluate(node, XPathConstants.NODESET);
      return nodeList.item(0).getFirstChild().getNodeValue();
    }
    catch(Exception e)
    {
    }

    throw new ConverterException(ConverterException.Action.AbandonRunWhenever, ConverterException.Log.Error, "Can't find parameter " + path);
  }





  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                           Local variables                              **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/

  /**************************************************************************/
  /* Singleton. */

  private static NumeralProcessorFactory m_Instance = null;


  /**************************************************************************/
  /* Used for XPath processing. */

  private XPath m_Xpath = (XPathFactory.newInstance()).newXPath();
}
